package com.jwsoft.manager.core.integration.eduAddressTree.impl;

import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.jwsoft.manager.common.enums.DataSaveEnum;
import com.jwsoft.manager.common.vo.eduAddressTree.AddressTreeVO;
import com.jwsoft.manager.common.vo.eduAddressTree.EduAddressTreeVO;
import com.jwsoft.manager.core.dao.model.EduAddressTree;
import com.jwsoft.manager.core.dao.model.EduAddressTreeSchool;
import com.jwsoft.manager.core.dao.service.EduAddressTreeSchoolService;
import com.jwsoft.manager.core.dao.service.EduAddressTreeService;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 叶子节点数量变化处理类
 *
 * @author fangs
 */
public class ReviseLeafNumHandler {

    private final EduAddressTreeService eduAddressTreeService;

    private final EduAddressTreeSchoolService eduAddressTreeSchoolService;

    /**
     * 用于存放变更节点之前的父节点 叶子树扣减信息
     */
    private final Map<Long, List<Integer>> oldMap = new HashMap<>();

    /**
     * 用于存放变更节点之前的父节点 学校绑定叶子树扣减信息
     */
    private final Map<Long, Map<String, Integer>> oldTreeSchoolMap = new HashMap<>();

    /**
     * 节点的叶子节点数信息
     */
    private Map<Long, List<Integer>> map = null;

    /**
     * 节点的学校绑定叶子节点数信息
     */
    private Map<Long, Map<String, Integer>> treeSchoolMap = null;

    /**
     * 变更过的节点
     */
    private Map<Long, EduAddressTreeVO> changeMap = null;

    /**
     * 变更地址树的层级列表
     */
    private List<EduAddressTreeVO> levelList = null;

    public ReviseLeafNumHandler(List<EduAddressTreeVO> changeList, EduAddressTreeService eduAddressTreeService, EduAddressTreeSchoolService eduAddressTreeSchoolService) {
        this.eduAddressTreeService = eduAddressTreeService;
        this.eduAddressTreeSchoolService = eduAddressTreeSchoolService;
        changeMap = changeList.stream().collect(Collectors.toMap(EduAddressTreeVO::getAddressId, v -> v, (k1, k2) -> k2));

        AddressTreeVO root = this.buildChangeTree(changeList);
        levelList = getLevelList(root);
        map = new HashMap<>((int) (levelList.size() / 0.75D + 1));
        treeSchoolMap = new HashMap<>((int) (levelList.size() / 0.75D + 1));
    }

    public void handle() {
        for (int i = levelList.size() - 1; i >= 0; i--) {
            reviseLeafNum(levelList.get(i));
        }
        if (!CollectionUtils.isEmpty(oldMap)) {
            handleOldParent();
        }
    }

    /**
     * 处理某一个节点的leafNum计算
     *
     * @param eduAddressTreeVO 当前计算的节点
     * @author fangs
     */
    private void reviseLeafNum(EduAddressTreeVO eduAddressTreeVO) {
        //减掉的叶子节点数
        int diff = 0;
        //减掉的leafNum node-school
        Map<String, Integer> schoolData = null;
        // 如果是修改的节点，直接查询数据库的子级节点修正叶子节点数
        if (changeMap.containsKey(eduAddressTreeVO.getAddressId())) {
            EduAddressTreeVO metaChangeData = changeMap.get(eduAddressTreeVO.getAddressId());
            //查询该节点绑定的学校的绑定叶子节点数
            QueryWrapper<EduAddressTreeSchool> queryWrapper = new QueryWrapper<>();
            queryWrapper.lambda().eq(EduAddressTreeSchool::getTreeId, eduAddressTreeVO.getAddressId())
                    .select(EduAddressTreeSchool::getSchoolId, EduAddressTreeSchool::getBoundLeafNum);
            List<EduAddressTreeSchool> eduAddressTreeSchools = eduAddressTreeSchoolService.list(queryWrapper);
            Map<String, Integer> oldSchoolData = eduAddressTreeSchools.stream().collect(Collectors.toMap(EduAddressTreeSchool::getSchoolId, EduAddressTreeSchool::getBoundLeafNum));

            if (DataSaveEnum.delete == metaChangeData.getChangeType()) {
                diff = -eduAddressTreeVO.getLeafNum();
                //该节点对应的学校绑定信息应该删除并统计 节点-学校的叶子节点数
                schoolData = new HashMap<>(oldSchoolData);
                schoolData.replaceAll((k, v) -> -oldSchoolData.get(k));
            } else {
                Long parentId = metaChangeData.getParentId();
                Long oldParentId = metaChangeData.getOldParentId();
                if (DataSaveEnum.update == metaChangeData.getChangeType() && !parentId.equals(oldParentId)) {
                    //如果是修改，那么之前的父节点需要减去当前节点的之前的数量
                    saveOldParentChange(metaChangeData, oldSchoolData);
                }
                AddressTree addressTree = AddressTree.getAddressTree(eduAddressTreeVO);
                addressTree.reviseLeafNum();
                Integer newLeafNum = addressTree.getLeafNum();
                //同步学校绑定信息中的leafNum
                Map<String, Integer> schoolBoundLeaf = addressTree.reviseLeafNumOfSchool();
                //不论是新增还是修改，当前父节点都要增加当前节点的数量
                diff = newLeafNum;
                schoolData = schoolBoundLeaf;
            }

        } else {
            schoolData = new HashMap<>();
            diff = evaluateLeafNumForNoChange(eduAddressTreeVO.getAddressId(), schoolData, map, treeSchoolMap);
        }
        //将当前节点的变动量加到父节点上
        saveChangeToParent(eduAddressTreeVO.getParentId(), diff, schoolData, map, treeSchoolMap);
    }

    /**
     * 将当前节点的变动量加到父节点上
     *
     * @param parentId   当前节点的父节点id
     * @param diff       当前节点的变动量
     * @param schoolData 当前节点对应的学校绑定叶子节点的变动量
     * @author fangs
     */
    private void saveChangeToParent(Long parentId, Integer diff, Map<String, Integer> schoolData,
                                    Map<Long, List<Integer>> map, Map<Long, Map<String, Integer>> treeSchoolMap) {
        if (map.containsKey(parentId)) {
            map.get(parentId).add(diff);
        } else {
            map.put(parentId, new ArrayList<>(Collections.singletonList(diff)));
        }
        //同步学校绑定信息中的leafNum
        if (!CollectionUtils.isEmpty(schoolData)) {
            Map<String, Integer> schoolBoundLeaf = null;
            if (treeSchoolMap.containsKey(parentId)) {
                schoolBoundLeaf = treeSchoolMap.get(parentId);
            } else {
                schoolBoundLeaf = new HashMap<>();
            }
            for (String schoolId : schoolData.keySet()) {
                if (schoolBoundLeaf.containsKey(schoolId)) {
                    schoolBoundLeaf.put(schoolId, schoolBoundLeaf.get(schoolId) + schoolData.get(schoolId));
                } else {
                    schoolBoundLeaf.put(schoolId, schoolData.get(schoolId));
                }
            }
            treeSchoolMap.put(parentId, schoolBoundLeaf);
        }
    }

    /**
     * 计算非变更节点的叶子节点数
     *
     * @param id         当前节点id
     * @param schoolData 当前节点对应的学校绑定叶子节点的变动量，用于传递处理后的节点，传入空map
     * @return 当前节点的叶子节点数变动量 diff
     * @author fangs
     */
    private Integer evaluateLeafNumForNoChange(Long id, Map<String, Integer> schoolData,
                                               Map<Long, List<Integer>> map, Map<Long, Map<String, Integer>> treeSchoolMap) {
        //如果是根节点，直接返回0
        if (id == 0) {
            return 0;
        }
        if (schoolData == null) {
            throw new IllegalArgumentException("schoolData不能为null");
        }
        int diff = 0;
        // 如果不是修改的节点，说明是修改节点的上级节点，根据修改节点的增量计算叶子节点数的变化
        List<Integer> list = map.get(id);
        if (!CollectionUtils.isEmpty(list)) {
            for (Integer integer : list) {
                diff += integer;
            }
        }
        if (diff != 0) {
            UpdateWrapper<EduAddressTree> updateWrapper = new UpdateWrapper<>();
            updateWrapper.lambda().eq(EduAddressTree::getAddressId, id)
                    .setSql("leaf_num = leaf_num + " + diff);
            eduAddressTreeService.update(updateWrapper);
        }
        //学校绑定的叶子节点数
        Map<String, Integer> schoolBoundLeafNum = treeSchoolMap.get(id);
        if (!CollectionUtils.isEmpty(schoolBoundLeafNum)) {
            for (String school : schoolBoundLeafNum.keySet()) {
                if (schoolData.containsKey(school)) {
                    schoolData.put(school, schoolData.get(school) + schoolBoundLeafNum.get(school));
                } else {
                    schoolData.put(school, schoolBoundLeafNum.get(school));
                }
                QueryWrapper<EduAddressTreeSchool> queryWrapper = new QueryWrapper<>();
                queryWrapper.lambda().select(EduAddressTreeSchool::getId).eq(EduAddressTreeSchool::getTreeId, id)
                        .eq(EduAddressTreeSchool::getSchoolId, school);
                EduAddressTreeSchool treeSchool = eduAddressTreeSchoolService.getOne(queryWrapper);
                if (treeSchool == null) {
                    eduAddressTreeSchoolService.saveTreeAndSchool(id, school, 0, schoolBoundLeafNum.get(school));
                } else {
                    UpdateWrapper<EduAddressTreeSchool> updateWrapper = new UpdateWrapper<>();
                    updateWrapper.lambda().eq(EduAddressTreeSchool::getId, treeSchool.getId())
                            .setSql("bound_leaf_num = bound_leaf_num + " + schoolBoundLeafNum.get(school));
                    eduAddressTreeSchoolService.update(updateWrapper);
                }
            }
        }
        return diff;
    }

    /**
     * 保存修改前的父节点的变动量
     *
     * @param metaChangeData 节点修改元数据
     * @param oldSchoolData  修正前的学校绑定信息
     * @author fangs
     */
    private void saveOldParentChange(EduAddressTreeVO metaChangeData, Map<String, Integer> oldSchoolData) {
        Integer oldLeafNum = metaChangeData.getLeafNum();
        if (oldMap.containsKey(metaChangeData.getOldParentId())) {
            oldMap.get(metaChangeData.getOldParentId()).add(-oldLeafNum);
        } else {
            oldMap.put(metaChangeData.getOldParentId(), new ArrayList<>(Collections.singletonList(-oldLeafNum)));
        }
        //学校绑定信息中的leafNum
        Map<String, Integer> oldSchoolBoundLeaf;
        if (oldTreeSchoolMap.containsKey(metaChangeData.getOldParentId()) && oldTreeSchoolMap.get(metaChangeData.getOldParentId()) != null) {
            oldSchoolBoundLeaf = oldTreeSchoolMap.get(metaChangeData.getOldParentId());
        } else {
            oldSchoolBoundLeaf = new HashMap<>();
        }
        for (String schoolId : oldSchoolData.keySet()) {
            if (oldSchoolBoundLeaf.containsKey(schoolId)) {
                oldSchoolBoundLeaf.put(schoolId, oldSchoolBoundLeaf.get(schoolId) - oldSchoolData.get(schoolId));
            } else {
                oldSchoolBoundLeaf.put(schoolId, -oldSchoolData.get(schoolId));
            }
        }
        oldTreeSchoolMap.put(metaChangeData.getOldParentId(), oldSchoolBoundLeaf);
    }

    /**
     * 处理移除节点的叶子节点数
     *
     * @author fangs
     */
    private void handleOldParent() {
        List<EduAddressTreeVO> oldParentList = new ArrayList<>();
        for (Long id : oldMap.keySet()) {
            EduAddressTree eduAddressTree = eduAddressTreeService.getById(id);
            if (eduAddressTree != null) {
                oldParentList.add(Convert.convert(EduAddressTreeVO.class, eduAddressTree));
            }
        }
        AddressTreeVO oldRoot = this.buildChangeTree(oldParentList);
        List<EduAddressTreeVO> levelList = getLevelList(oldRoot);
        for (EduAddressTreeVO eduAddressTreeVO : levelList) {
            Map<String, Integer> schoolData = new HashMap<>();
            int diff = evaluateLeafNumForNoChange(eduAddressTreeVO.getAddressId(), schoolData, oldMap, oldTreeSchoolMap);
            //将当前节点的变动量加到父节点上
            saveChangeToParent(eduAddressTreeVO.getParentId(), diff, schoolData, oldMap, oldTreeSchoolMap);
        }
    }

    /**
     * 构建变更节点的地址树
     *
     * @param changeList 变更节点
     * @return 地址树根节点
     * @author fangs
     */
    public AddressTreeVO buildChangeTree(List<EduAddressTreeVO> changeList) {
        Map<Long, AddressTreeVO> nodeMap =
                changeList.stream().collect(Collectors.toMap(EduAddressTreeVO::getAddressId, v -> Convert.convert(AddressTreeVO.class, v)));
        //用于记录已经处理过的节点
        Set<Long> already = new HashSet<>();
        AddressTreeVO root = new AddressTreeVO();
        root.setAddressId(0L);
        root.setChildNode(new ArrayList<>());
        for (EduAddressTreeVO eduAddressTreeVO : changeList) {
            AddressTreeVO addressTreeVO = nodeMap.get(eduAddressTreeVO.getAddressId());
            buildOneChangeTree(addressTreeVO, already, root, nodeMap);
        }
        return root;
    }

    /**
     * 构建单个变更节点的地址树
     *
     * @param addressTreeVO 当前节点
     * @param already       已经处理过的节点
     * @param root          地址树根节点
     * @param nodeMap       节点缓存map
     * @author fangs
     */
    private void buildOneChangeTree(AddressTreeVO addressTreeVO, Set<Long> already, AddressTreeVO root, Map<Long, AddressTreeVO> nodeMap) {
        while (true) {
            //如果当前节点已经处理过，说明当前节点是其他节点的父节点，跳出循环
            if (already.contains(addressTreeVO.getAddressId())) {
                break;
            }
            already.add(addressTreeVO.getAddressId());
            //父节点id为0，说明是根节点
            if (addressTreeVO.getParentId() == 0) {
                root.getChildNode().add(addressTreeVO);
                break;
            }
            //从修改的节点中获取父节点
            AddressTreeVO parentVO = nodeMap.get(addressTreeVO.getParentId());
            //如果当前父节点在修改的节点中不存在，从数据库中获取
            if (parentVO == null) {
                EduAddressTree eduAddressTree = eduAddressTreeService.getById(addressTreeVO.getParentId());
                //如果当前父节点不是根节点，并且在数据库中不存在，说明数据有问题
                if (eduAddressTree == null) {
                    return;
                }
                parentVO = Convert.convert(AddressTreeVO.class, eduAddressTree);
                nodeMap.put(parentVO.getAddressId(), parentVO);
            }
            //将当前节点添加到父节点的子节点中
            List<AddressTreeVO> parentVOChildNode = parentVO.getChildNode();
            if (CollectionUtils.isEmpty(parentVOChildNode)) {
                parentVOChildNode = Convert.toList(AddressTreeVO.class, addressTreeVO);
                parentVO.setChildNode(parentVOChildNode);
            } else {
                parentVOChildNode.add(addressTreeVO);
            }
            //将父节点设置为当前节点，继续循环
            addressTreeVO = parentVO;
        }
    }

    /**
     * 获取地址树的层级列表
     *
     * @param root 根节点
     * @return 层级列表
     * @author fangs
     */
    public List<EduAddressTreeVO> getLevelList(AddressTreeVO root) {
        List<EduAddressTreeVO> list = new ArrayList<>();
        Queue<AddressTreeVO> queue = new LinkedList<>();
        queue.add(root);
        while (queue.size() > 0) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                AddressTreeVO poll = queue.poll();
                EduAddressTreeVO eduAddressTreeVO = Convert.convert(EduAddressTreeVO.class, poll);
                list.add(eduAddressTreeVO);
                List<AddressTreeVO> childNode = poll.getChildNode();
                if (CollectionUtils.isEmpty(childNode)) {
                    continue;
                }
                queue.addAll(childNode);
            }
        }
        return list;
    }
}
